/*	--- 使用条件装配 --- 

开发【应用程序】时，我们会使用【开发环境】

例如，开发时, 使用【内存数据库】以便快速启动。

而运行在【生产环境】时，我们会使用【生产环境】，例如，使用【MySQL数据库】。

------------------------------------


如果【应用程序】可以根据【自身的环境】做一些适配，无疑会更加灵活。

【Spring为应用程序】准备了【Profile】这一概念，用来表示【不同的环境】。


例如，我们分别定义【开发、测试、生产】这3个环境：
	· native
	· test
	· production


创建【某个Bean】时，【Spring容器】可以根据【注解@Profile】来决定【是否创建】。

例如，以下配置： */
@Configuration
@ComponentScan
public class AppConfig {
	@Bean
	@Profile("!test")
	ZoneId createZoneId() {
		return ZoneId.systemDefault();
	}

	@Bean 
	@Profile("test") 
	ZoneId createZoneIdForTest() {
		return ZoneId.of("America/New_York");
	}
}

/*
如果【当前的Profile】设置为【test】，则【Spring容器】会调用 createZoneIdForTest() 创建【ZoneId】，

否则(如果环境不是test)，调用createZoneId()创建ZoneId。注意到@Profile("!test")表示非test环境。


-------------------------------------

在【运行程序】时，加上【JVM参数】: 
	-Dspring.profiles.active=test 

就可以指定【以test环境启动】。


实际上，【Spring】允许指定【多个Profile】，例如： 
	-Dspring.profiles.active=test,master

可以表示test环境，并使用master分支代码。

----------------------------------

要满足【多个Profile条件】，可以这样写： */
@Bean 
@Profile( { "test", "master" } ) // 同时满足test和master
ZoneId createZoneId() {
	//...
}


/*==========================================================


#	使用Conditional

除了根据【@Profile条件】来决定【是否创建某个Bean】外，【Spring】还可以根据【@Conditional】决定【是否创建某个Bean】。

例如，我们对【SmtpMailService】添加【如下注解】： */
@Component 
@Conditional(onSmtpEnvCondition.class) // 满足class onSmtpEnvCondition的条件
public class SmtpMailService implements MailService {
	//...
}

/*
它的意思是，如果满足【OnSmtpEnvCondition的条件】，才会创建【SmtpMailService】这个Bean。

【class OnSmtpEnvCondition的条件】是什么呢？
我们看一下代码： */
public class OnSmtpEnvCondition implements Condition {
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 
		// boolean matches(ConditionContext, AnnotatedTypeMetadata)
		return "true".equalsIgnoreCase(System.getenv("smtp"));
	}
}


/*
因此，【class OnSmtpEnvCondition的条件】是【存在 环境变量smtp，值为true】。


--------------------------------------

这样，我们就可以通过【环境变量】来控制【是否创建SmtpMailService】。

【Spring】只提供了【@Conditional】注解，具体判断逻辑还需要我们自己实现。

【Spring Boot】提供了更多【使用起来更简单的条件注解】

	@ConditionalOnProperty(name="Property_name", havingValue="Proprety_value")

例如，如果【配置文件】中存在【app.smtp=true】，则【创建MailService】： */
@Component
@ConditionalOnProperty(name="app.smtp", havingValue="true")
public class MailService {
	//...
}


/*
如果【当前classpath中】存在【class javax.mail.Transport】，则【创建MailService】： 

	@ConditionalOnClass(name = "class_name")
*/
@Component
@ConditionalOnClass(name = "javax.mail.Transport")
public class MailService {
	//...
}


/*
后续我们会介绍【Spring Boot的条件】装配。

-----------------------------------

我们以【文件存储】为例，假设我们需要: 
	【保存用户上传的头像】，并返回【存储路径】

在【本地开发】运行时，我们总是把【数据】存储到【文件】： */
@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "file", matchIfMissing = true) 
public class FileUploader implements Uploader {
	//...
}

/*
在【生产环境】运行时，我们会把【文件】存储到【类似AWS S3】上： */
@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "s3")
public class S3Uploader implements Uploader {
	//...
}


/*
其他【需要存储的服务】则注入【Uploader】： */
@Component
public class UserImageService {
	@Autowired
	Uploader uploader;
}


/*
当【应用程序】检测到【配置文件】存在【app.storage=s3】时，自动使用【S3Uploader】

如果存在【配置 app.storage=file】，或者【配置 app.storage 不存在】， 则使用【FileUploader】。

可见，使用【条件注解】，能【更灵活地 装配Bean】。


========================================================================


#	练习

使用@Profile进行条件装配 */


/*=====================================================================

#	----- 使用条件装配Bean の 小结 ----- 

1. 【Spring】允许通过【@Profile】配置【不同的Bean】；

	@Profile("native/test/production/!...")
	@Profile( { "test", "master" } ) // 同时满足test和master


2. 【Spring】还提供了【@Conditional】来进行【条件装配】

	@Conditional(implements_Condition_Class.class)
		// 满足Condition.matches(ConditionContext, AnnotatedTypeMetadata)


3. 【Spring Boot】在此基础上进一步提供了【基于配置、Class、Bean等条件】进行【装配】。

	@ConditionalOnProperty(name="Configuration_property", havingValue="property_value")

	@ConditionalOnProperty(name = "app.storage", havingValue = "file", matchIfMissing = true)

	@ConditionalOnClass(name = "javax.mail.Transport")

*/


























